/**
 * Copyright (c) 2011 Ibrahim Abd Elkader <i.abdalkader@gmail.com> 
 * See the file COPYING for copying permission.
 */
#include <stdio.h>
#include <string.h>
#include <lpc17xx_i2c.h>
#include <lpc17xx_pwm.h>
#include <lpc17xx_gpio.h>
#include <lpc17xx_pinsel.h>
#include "lzf.h"
#include "nrf24l01p.h"
#define TCM_VBLK        (11) 
#define TCM_HBLK        (12)
#define TCM_DCLK        (13)
#define INT_PORT        (2)
#define TCM_I2C_SDA     (0)
#define TCM_I2C_SCL     (1)
#define TCM_I2C_PORT    (0)
#define TCM_I2C_ADDR    (0x3C)
#define TCM_I2C_BASE    (LPC_I2C1)
#define TCM_I2C_CLCK    (100000)
#define INT_RISING      (0)
#define INT_FALLING     (1)
#define INT_VD          ((1<<TCM_VBLK))
#define INT_HD          ((1<<TCM_HBLK))
#define INT_DK          ((1<<TCM_DCLK))
#define INT_VD_HD       ((1<<TCM_VBLK) | (1<<TCM_HBLK))
#define IMAGE_SIZE      (128 * 96 * 1) /* width * height * bpp */
#define HEADER_LENGTH   (32)
#define STLED_PORT      (1) 
#define STLED_PIN       (29)
#define PWM_CHANNEL     (6)
#define PWM_CLKDIV      (10)    /* EXTCLK = C_CLK / PWM_CLKDIV */
#define T_VDD           (100)
#define T_EXTCLK        (10)    /* 100 EXTCLK cycles */
#define T_RESET         (200)   /* 2000 EXTCLK cycles */
volatile int bytes = 0;
/* compressed frame buffer */
volatile uint8_t frame_out[6 * 1024];
/* frame buffer placed in ram2*/
volatile uint8_t frame_buffer[IMAGE_SIZE] __attribute__ ((section (".ram2"))); 

void flash_stled()
{
    GPIO_SetValue(STLED_PORT, (1<<STLED_PIN));
    sleep(1);
    GPIO_ClearValue(STLED_PORT, (1<<STLED_PIN));
    sleep(1);
}

uint8_t tcm_read(uint8_t addr)
{
    uint8_t buf;
    I2C_M_SETUP_Type tfr_cfg = {
        .tx_data     = &addr,
        .tx_length   = sizeof(addr),
        .rx_data     = &buf,
        .rx_length   = sizeof(buf),
        .sl_addr7bit = TCM_I2C_ADDR,
        .retransmissions_max = 3,
    };

    /* write register address/read register value */
    I2C_MasterTransferData(TCM_I2C_BASE, &tfr_cfg, I2C_TRANSFER_POLLING);
    return buf;
}

void tcm_write(uint8_t addr, uint8_t data)
{
    uint8_t buf[2] = {addr, data};
    I2C_M_SETUP_Type tfr_cfg = {
        .tx_data     = buf,
        .tx_length   = sizeof(buf),
        .rx_data     = 0,
        .rx_length   = 0,
        .sl_addr7bit = TCM_I2C_ADDR,
        .retransmissions_max = 3,
    };

    /* write register address/read register value */
    I2C_MasterTransferData(TCM_I2C_BASE, &tfr_cfg, I2C_TRANSFER_POLLING);
}

void init_extclk(int div)
{
    PWM_TIMERCFG_Type PWMCfgDat;
    PWM_MATCHCFG_Type PWMMatchCfgDat;
    PINSEL_CFG_Type PinCfg;

    /* PWM prescale value = 1 (absolute value - tick value) */
    PWMCfgDat.PrescaleOption = PWM_TIMER_PRESCALE_TICKVAL;
    PWMCfgDat.PrescaleValue = 1;
    PWM_Init(LPC_PWM1, PWM_MODE_TIMER, (void *) &PWMCfgDat);

    PinCfg.Funcnum = 1;
    PinCfg.OpenDrain = 0;
    PinCfg.Pinmode = 0;
    PinCfg.Portnum = 2;
    PinCfg.Pinnum = 5;
    PINSEL_ConfigPin(&PinCfg);

    /* Set match value for PWM match channel 0 = 256, update immediately */
    PWM_MatchUpdate(LPC_PWM1, 0, div, PWM_MATCH_UPDATE_NOW);
    PWMMatchCfgDat.IntOnMatch = DISABLE;
    PWMMatchCfgDat.MatchChannel = 5;
    PWMMatchCfgDat.ResetOnMatch = ENABLE;
    PWMMatchCfgDat.StopOnMatch = DISABLE;
    PWM_ConfigMatch(LPC_PWM1, &PWMMatchCfgDat);

    PWM_ChannelConfig(LPC_PWM1, PWM_CHANNEL, PWM_CHANNEL_SINGLE_EDGE);
    PWM_MatchUpdate(LPC_PWM1, PWM_CHANNEL, div/2, PWM_MATCH_UPDATE_NOW); /*duty cycle 50%*/
    /* Configure match option */
    PWMMatchCfgDat.IntOnMatch = DISABLE;
    PWMMatchCfgDat.MatchChannel = PWM_CHANNEL;
    PWMMatchCfgDat.ResetOnMatch = DISABLE;
    PWMMatchCfgDat.StopOnMatch = DISABLE;
    PWM_ConfigMatch(LPC_PWM1, &PWMMatchCfgDat);
    /* Enable PWM Channel Output */
    PWM_ChannelCmd(LPC_PWM1, PWM_CHANNEL, ENABLE);

    /* Reset and Start counter */
    PWM_ResetCounter(LPC_PWM1);
    PWM_CounterCmd(LPC_PWM1, ENABLE);

    /* Start PWM now */
    PWM_Cmd(LPC_PWM1, ENABLE);
}

void tcm_init()
{
    PINSEL_CFG_Type pin_cfg={
        .Funcnum    = 3,
        .Portnum    = TCM_I2C_PORT,
        .Pinmode    = PINSEL_PINMODE_PULLUP,
        .OpenDrain  = PINSEL_PINMODE_NORMAL
    };

    /* configure I2C SDA */
    pin_cfg.Pinnum = TCM_I2C_SDA;
    PINSEL_ConfigPin(&pin_cfg);

    /* configure I2C SCL */
    pin_cfg.Pinnum = TCM_I2C_SCL;
    PINSEL_ConfigPin(&pin_cfg);    

    /* init I2C */
    I2C_Init(TCM_I2C_BASE, TCM_I2C_CLCK);
    I2C_Cmd(TCM_I2C_BASE, ENABLE);

    /* configure status LED */
    pin_cfg.Funcnum = 0;
    pin_cfg.Pinnum  = STLED_PIN;
    pin_cfg.Portnum = STLED_PORT;

    PINSEL_ConfigPin(&pin_cfg);
    GPIO_SetDir(STLED_PORT, (1<<STLED_PIN), 1);
    GPIO_ClearValue(STLED_PORT, (1<<STLED_PIN));

    /* configure DCLK pin port 2, byte 2, mask */
    FIO_SetMask(2, (1<<TCM_DCLK), 0);
    FIO_SetDir(2, (1<<TCM_DCLK), 0);
  
    /* configure data bus port 1, byte 2, mask */
    FIO_ByteSetMask(1, 2, 0xFF, 0);
    FIO_ByteSetDir (1, 2, 0xFF, 0);

    /* configure reset pin port 2, byte 1, mask 2.8*/
    FIO_ByteSetMask(2, 1, 0x01, 0);     
    FIO_ByteSetDir (2, 1, 0x01, 1);
    /* pull reset pin down*/
    FIO_ByteClearValue(2, 1, 0x01);

    /* init EXTCLK */
    sleep(T_VDD);
    init_extclk(PWM_CLKDIV);
    sleep(T_EXTCLK);

    /* release reset */
    FIO_ByteSetValue(2, 1, 0x01);
    sleep(T_RESET);
    
    /* 15 FPS */
    tcm_write(0x02, 0x00);
    /* DOUT/subQCIF(8)/RGB/COLOR */
    tcm_write(0x03, 0x22); 
    printf("tcm: 0x%x\n", tcm_read(0x1E));

    /* enable VD GPIO interrupt on rising edge */
    NVIC_DisableIRQ(EINT3_IRQn); 
    FIO_IntCmd(INT_PORT, INT_VD, INT_RISING);
    NVIC_EnableIRQ(EINT3_IRQn);              

    *frame_out = 0xAA; //SOF
    uint32_t *length = (uint32_t *) (frame_out+1);
    while (1) {
        if (bytes == IMAGE_SIZE) {
            *length = lzf_compress((const void *) frame_buffer, IMAGE_SIZE, (void *) frame_out+HEADER_LENGTH, sizeof(frame_out)-HEADER_LENGTH);
            if (*length != 0) {
                nrf_send((uint8_t *) frame_out, HEADER_LENGTH + *length);
            }
        }
    }
}
